/*
 * Copyright 1999-2018 Alibaba Group Holding Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.ouwen.smartpay.service.chain.spi;

import com.ouwen.smartpay.service.chain.slotchain.ProcessorSlot;
import org.apache.http.util.Asserts;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/***
 *@author <a href="http://youngitman.tech">青年IT男</a>
 *@version v1.0.0
 *@className SpiLoader
 *@description Spi加载器
 *@JunitTest: {@link  }
 *@date 11:11 2020-04-22
 *
**/
public final class SpiLoader {

    static Logger logger = LoggerFactory.getLogger(SpiLoader.class);

    private static final Map<String, ServiceLoader> SERVICE_LOADER_MAP = new ConcurrentHashMap<>();

    /***
     *
     * 获取第一个被查找到的{@link Class}
     *
     * @author liyong
     * @date 11:19 2020-04-22
     * @param clazz
     * @exception
     * @return T
     **/
    public static <T> T loadFirstInstance(Class<T> clazz) {
        Asserts.notNull(clazz, "SPI class cannot be null");
        try {
            String key = clazz.getName();
            // Not thread-safe, as it's expected to be resolved in a thread-safe context.
            ServiceLoader<T> serviceLoader = SERVICE_LOADER_MAP.get(key);
            if (serviceLoader == null) {
                serviceLoader = ServiceLoaderUtil.getServiceLoader(clazz);
                SERVICE_LOADER_MAP.put(key, serviceLoader);
            }

            Iterator<T> iterator = serviceLoader.iterator();
            if (iterator.hasNext()) {
                return iterator.next();
            } else {
                return null;
            }
        } catch (Throwable t) {
            logger.error("[SpiLoader] ERROR: loadFirstInstance failed", t);
            t.printStackTrace();
            return null;
        }
    }

    /***
     *
     * 加载指定{@link Class}的实例并且缓存
     *
     * @author liyong
     * @date 11:18 2020-04-22
     * @param clazz
     * @param defaultClass
     * @exception
     * @return T
     **/
    public static <T> T loadFirstInstanceOrDefault(Class<T> clazz, Class<? extends T> defaultClass) {
        Asserts.notNull(clazz, "SPI class cannot be null");
        Asserts.notNull(defaultClass, "default SPI class cannot be null");
        try {
            String key = clazz.getName();
            // Not thread-safe, as it's expected to be resolved in a thread-safe context.
            ServiceLoader<T> serviceLoader = SERVICE_LOADER_MAP.get(key);
            if (serviceLoader == null) {
                serviceLoader = ServiceLoaderUtil.getServiceLoader(clazz);
                SERVICE_LOADER_MAP.put(key, serviceLoader);
            }

            for (T instance : serviceLoader) {
                if (instance.getClass() != defaultClass) {
                    return instance;
                }
            }
            return defaultClass.newInstance();
        } catch (Throwable t) {
            logger.error("[SpiLoader] ERROR: loadFirstInstanceOrDefault failed", t);
            t.printStackTrace();
            return null;
        }
    }

   /***
    *
    * 加载最高优先级{@link Class}的实例，通过注解{@link SpiOrder}指定顺序
    *
    * @author liyong
    * @date 11:19 2020-04-22
    * @param clazz
    * @exception
    * @return T
    **/
    public static <T> T loadHighestPriorityInstance(Class<T> clazz) {
        try {
            String key = clazz.getName();
            // Not thread-safe, as it's expected to be resolved in a thread-safe context.
            ServiceLoader<T> serviceLoader = SERVICE_LOADER_MAP.get(key);
            if (serviceLoader == null) {
                serviceLoader = ServiceLoaderUtil.getServiceLoader(clazz);
                SERVICE_LOADER_MAP.put(key, serviceLoader);
            }

            SpiOrderWrapper<T> w = null;
            for (T spi : serviceLoader) {
                int order = SpiOrderResolver.resolveOrder(spi);
                logger.info("[SpiLoader] Found {} SPI: {} with order {}", clazz.getSimpleName(),
                        spi.getClass().getCanonicalName(), order);
                if (w == null || order < w.order) {
                    w = new SpiOrderWrapper<>(order, spi);
                }
            }
            return w == null ? null : w.spi;
        } catch (Throwable t) {
            logger.error("[SpiLoader] ERROR: loadHighestPriorityInstance failed", t);
            t.printStackTrace();
            return null;
        }
    }

    /***
     *
     * 返回被加载的列表
     *
     * @author liyong
     * @date 11:20 2020-04-22
     * @param clazz
     * @exception
     * @return java.util.List<T>
     **/
    public static <T> List<T> loadInstanceList(Class<T> clazz) {
        try {
            String key = clazz.getName();
            // Not thread-safe, as it's expected to be resolved in a thread-safe context.
            ServiceLoader<T> serviceLoader = SERVICE_LOADER_MAP.get(key);
            if (serviceLoader == null) {
                serviceLoader = ServiceLoaderUtil.getServiceLoader(clazz);
                SERVICE_LOADER_MAP.put(key, serviceLoader);
            }

            List<T> list = new ArrayList<>();
            for (T spi : serviceLoader) {
                logger.info("[SpiLoader] Found {} SPI: {}", clazz.getSimpleName(),
                        spi.getClass().getCanonicalName());
                list.add(spi);
            }
            return list;
        } catch (Throwable t) {
            logger.error("[SpiLoader] ERROR: loadInstanceList failed", t);
            t.printStackTrace();
            return new ArrayList<>();
        }
    }

   /***
    *
    * 加载和排序，返回被加载的列表
    *
    * @author liyong
    * @date 11:21 2020-04-22
    * @param clazz
    * @exception
    * @return java.util.List<T>
    **/
    public static <T> List<T> loadInstanceListSorted(Class<T> clazz) {
        try {
            String key = clazz.getName();
            // Not thread-safe, as it's expected to be resolved in a thread-safe context.
            ServiceLoader<T> serviceLoader = SERVICE_LOADER_MAP.get(key);
            if (serviceLoader == null) {
                serviceLoader = ServiceLoaderUtil.getServiceLoader(clazz);
                SERVICE_LOADER_MAP.put(key, serviceLoader);
            }

            List<SpiOrderWrapper<T>> orderWrappers = new ArrayList<>();
            for (T spi : serviceLoader) {
                int order = SpiOrderResolver.resolveOrder(spi);
                // Since SPI is lazy initialized in ServiceLoader, we use online sort algorithm here.
                SpiOrderResolver.insertSorted(orderWrappers, spi, order);
                logger.info("[SpiLoader] Found {} SPI: {} with order {}", clazz.getSimpleName(),
                        spi.getClass().getCanonicalName(), order);
            }
            List<T> list = new ArrayList<>(orderWrappers.size());
            for (int i = 0; i < orderWrappers.size(); i++) {
                list.add(orderWrappers.get(i).spi);
            }
            return list;
        } catch (Throwable t) {
            logger.error("[SpiLoader] ERROR: loadInstanceListSorted failed", t);
            t.printStackTrace();
            return new ArrayList<>();
        }
    }

    public static <T> List<T> loadPrototypeInstanceListSorted(Class<T> clazz) {
        try {
            // Not use SERVICE_LOADER_MAP, to make sure the instances loaded are different.
            ServiceLoader<T> serviceLoader = ServiceLoaderUtil.getServiceLoader(clazz);

            List<SpiOrderWrapper<T>> orderWrappers = new ArrayList<>();
            for (T spi : serviceLoader) {
                int order = SpiOrderResolver.resolveOrder(spi);
                // Since SPI is lazy initialized in ServiceLoader, we use online sort algorithm here.
                SpiOrderResolver.insertSorted(orderWrappers, spi, order);
                logger.debug("[SpiLoader] Found {} SPI: {} with order {}", clazz.getSimpleName(),
                        spi.getClass().getCanonicalName(), order);
            }
            List<T> list = new ArrayList<>(orderWrappers.size());
            for (int i = 0; i < orderWrappers.size(); i++) {
                list.add(orderWrappers.get(i).spi);
            }
            return list;
        } catch (Throwable t) {
            logger.error("[SpiLoader] ERROR: loadPrototypeInstanceListSorted failed", t);
            t.printStackTrace();
            return new ArrayList<>();
        }
    }

    public static void main(String[] args) {
        for(int i=0;i<5;i++){
            List<ProcessorSlot> processorSlots = loadPrototypeInstanceListSorted(ProcessorSlot.class);
            processorSlots.forEach(processorSlot -> System.out.println(processorSlot));
        }
    }

    private static class SpiOrderResolver {
        private static <T> void insertSorted(List<SpiOrderWrapper<T>> list, T spi, int order) {
            int idx = 0;
            for (; idx < list.size(); idx++) {
                if (list.get(idx).getOrder() > order) {
                    break;
                }
            }
            list.add(idx, new SpiOrderWrapper<>(order, spi));
        }

        private static <T> int resolveOrder(T spi) {
            if (!spi.getClass().isAnnotationPresent(SpiOrder.class)) {
                // Lowest precedence by default.
                return SpiOrder.LOWEST_PRECEDENCE;
            } else {
                return spi.getClass().getAnnotation(SpiOrder.class).value();
            }
        }
    }

    private static class SpiOrderWrapper<T> {
        private final int order;
        private final T spi;

        SpiOrderWrapper(int order, T spi) {
            this.order = order;
            this.spi = spi;
        }

        int getOrder() {
            return order;
        }

        T getSpi() {
            return spi;
        }
    }

    private SpiLoader() {
    }
}
